package com.jike.shop.cloud.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jike.shop.cloud.IGoodService;
import com.jike.shop.cloud.service.IGoodDetailService;
import com.jike.shop.cloud.service.IGoodSkuService;
import com.jike.shop.cloud.IUserService;
import com.jike.shop.cloud.common.exception.ApiException;
import com.jike.shop.cloud.dto.goods.GoodsDetailDto;
import com.jike.shop.cloud.dto.goods.GoodsInfoDto;
import com.jike.shop.cloud.model.common.LockStockInfo;
import com.jike.shop.cloud.model.common.StockInfo;
import com.jike.shop.cloud.mapper.GoodsInfoMapper;
import com.jike.shop.cloud.model.goods.GoodsDetail;
import com.jike.shop.cloud.model.goods.GoodsInfo;
import com.jike.shop.cloud.model.goods.GoodsSku;
import com.jike.shop.cloud.req.GoodsListReq;
import com.jike.shop.cloud.req.LockStockReq;
import com.jike.shop.cloud.req.UnlockStockReq;
import com.jike.shop.cloud.user.dto.Authentication;
import com.jike.shop.cloud.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StringUtils;


import java.util.Set;

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

@DubboService(group = "jike-shop", version = "1.0.0", protocol = "dubbo")
@Slf4j
public class GoodServiceImpl extends ServiceImpl<GoodsInfoMapper, GoodsInfo> implements IGoodService {
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private IGoodService goodService;

    @Autowired
    private IGoodSkuService skuService;

    @Autowired
    private IGoodDetailService goodDetailService;

    @Autowired
    private JsonUtil jsonUtil;

    @DubboReference(group = "jike-shop", version = "1.0.0")
    private IUserService userService;

    public static final String STOCK_KEY_PREFIX = "STOCK:";

    public static final String CREATE_SKU_PREFIX = "CREATE_SKU:";

    public static final String ORDERED_SKU_KEY = "ORDERED_SKU";


    // todo
    // 配置中心
    public static final long PAY_DURATION = 30 * 60 * 1000;

    private static final int DEFAULT_TIMEOUT_DURATION = 1000;

    @Override
    public void lockStock(LockStockReq req) {
        // 1. 验证用户信息
        validUser();

        // 2. 幂等性校验
        // todo
        // 性能问题： 1.key(调用方) + 有效期 2. 本地缓存 3. 前置校验 4. Reddison 公共Map => JVM
        // 兜底问题: try catch, 各种类型异常处理 => 业务流程 => 接口降级 => 库存补救措施
        Set<Object> members = redisTemplate.opsForSet().members(ORDERED_SKU_KEY);
        if(members != null) {
            for (Object member : members) {
                LockStockInfo lockStockInfo = (LockStockInfo) member;
                if(lockStockInfo.getOrderCode().equals(req.getOrderCode())) {
                    throw new ApiException("该订单已占用库存");
                }
            }
        }

        // 3. 锁库存
        Long skuId = req.getSkuId();
        String key = "STOCK:" + skuId;
        if (FALSE.equals(redisTemplate.hasKey(key))) {
            // 未找到缓存中的商品库存信息
            // 获取分布式锁
            if (TRUE.equals(redisTemplate.opsForValue().setIfAbsent(CREATE_SKU_PREFIX + skuId, 1, DEFAULT_TIMEOUT_DURATION, MILLISECONDS))) {
                // 成功获取锁
                GoodsSku sku = skuService.getById(skuId);
                if (sku == null) {
                    throw new ApiException("未找到该商品sku库存");
                }
                redisTemplate.opsForValue().set(STOCK_KEY_PREFIX + skuId, new StockInfo(sku.getStockNum(), 0));
                // 删除分布式锁
                redisTemplate.delete(CREATE_SKU_PREFIX + skuId);
            }
        }

        if (FALSE.equals(redisTemplate.hasKey(key))) {
            throw new ApiException("未找到该商品sku库存");
        }

        StockInfo stock = (StockInfo) redisTemplate.opsForValue().get(STOCK_KEY_PREFIX + skuId);

        if (stock == null) {
            throw new ApiException("未找到该商品sku库存");
        }

        // 判断是否存在可预订的库存数
        if (stock.getOrderedNum() >= stock.getStockNum()) {
            throw new ApiException("库存为0");
        }

        Integer stockNum = req.getNum();
        // todo
        // 并发问题: 异步处理(MQ?)

        // 增加预订库存数
        redisTemplate.opsForValue().set(STOCK_KEY_PREFIX + skuId, new StockInfo(stock.getStockNum(), stock.getOrderedNum() + stockNum));
        // 加锁(ORDERED_SKU:订单编号:sku Id)
        redisTemplate.opsForSet().add(ORDERED_SKU_KEY, new LockStockInfo(req.getOrderCode(), skuId, stockNum, System.currentTimeMillis()));
    }

    @Override
    public void unlockStock(UnlockStockReq req) {
        // 1. 验证用户信息
        validUser();

        Long skuId = req.getSkuId();

        Set<Object> members = redisTemplate.opsForSet().members(ORDERED_SKU_KEY);
        if(members != null) {
            for (Object member : members) {
                LockStockInfo lockStockInfo = (LockStockInfo) member;
                if(lockStockInfo.getSkuId().equals(req.getSkuId())) {
                    releaseOrderedStock(STOCK_KEY_PREFIX + skuId, lockStockInfo);
                }
            }
        }
    }

    private void releaseOrderedStock(String key, LockStockInfo lockStockInfo) {
        // 超时, 释放库存
        redisTemplate.opsForSet().remove(ORDERED_SKU_KEY, lockStockInfo);

        if(TRUE.equals(redisTemplate.hasKey(key))) {
            // 减少预订库存数
            StockInfo stock = (StockInfo) redisTemplate.opsForValue().get(key);
            if(stock != null) {
                redisTemplate.opsForValue()
                        .set(key, new StockInfo(stock.getStockNum(), stock.getOrderedNum() - lockStockInfo.getNum()));
            }
        }
    }

    /**
     * 检查过期已锁库存
     */
    @Override
    public void checkLockStock() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            Set<Object> members = redisTemplate.opsForSet().members(ORDERED_SKU_KEY);
            if (members != null) {
                for (Object member : members) {
                    LockStockInfo lockStockInfo = (LockStockInfo) member;
                    Long skuId = lockStockInfo.getSkuId();
                    if (System.currentTimeMillis() - PAY_DURATION > lockStockInfo.getTimeMills()) {
                        releaseOrderedStock(STOCK_KEY_PREFIX + skuId, lockStockInfo);
                    }
                }
            }

        }
    }

    @Override
    public IPage<GoodsInfoDto> getList(GoodsListReq req) {
        return lambdaQuery()
                .like(!StringUtils.isEmpty(req.getGoodsName()), GoodsInfo::getName, req.getGoodsName())
                .eq(GoodsInfo::getIsDeleted, false)
                .page(new Page<>(req.getPage(), req.getSize()))
                .convert(goods -> {
                    GoodsInfoDto goodsInfoDto = new GoodsInfoDto();
                    BeanUtils.copyProperties(goods, goodsInfoDto);
                    return goodsInfoDto;
                });
    }

    @Override
    public GoodsDetailDto detail(Long goodsId) {
        GoodsDetail goodsDetail = goodDetailService.lambdaQuery()
                .eq(GoodsDetail::getGoodsId, goodsId)
                .one();

        GoodsDetailDto goodsDetailDto = new GoodsDetailDto();
        BeanUtils.copyProperties(goodsDetail, goodsDetailDto);
        return goodsDetailDto;
    }

    private void validUser() {
        Authentication authentication;
        try {
            authentication = userService.login("jikeshop", "12345678");
            log.info("authentication: {}", authentication);
        }catch (Exception e) {
            throw new ApiException("非授权用户");
        }

        if (authentication == null) {
            throw new ApiException("非授权用户");
        }

        // 判断token过期时间是否小于支付时间(30分钟)
        if(authentication.getExpiresIn() < PAY_DURATION) {
            Authentication refresh = userService.refresh(authentication.getRefreshToken());
            log.info("refresh: {}", refresh);
        }
    }
}
